package me.chyxion.jdbc;

import lombok.val;
import java.util.Map;
import lombok.Getter;
import java.util.List;
import java.sql.Connection;
import javax.sql.DataSource;
import java.util.Collection;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;

/**
 * @version 0.0.1
 * @since 0.0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Mar 20, 2016 12:20:59 PM
 */
@Slf4j
public class NewbieJdbcImpl implements NewbieJdbc {
    @Getter
	private final DataSource dataSource;
	private CustomResolver customResolver;

	/**
	 * @param dataSource database data source
	 */
	public NewbieJdbcImpl(final DataSource dataSource) {
		this(dataSource, null);
	}

	/**
	 * @param dataSource database data source
	 * @param customResolver custom resolver
	 */
	public NewbieJdbcImpl(final DataSource dataSource,
						  final CustomResolver customResolver) {
		if (dataSource == null) {
			throw new IllegalArgumentException(
				"Data source could not be null");
		}
		this.dataSource = dataSource;

		if (customResolver == null) {
			log.info("Custom resolver is not provided, use default.");
			this.customResolver = new DefaultCustomResolver();
		}
		else {
			this.customResolver = customResolver;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T findValue(final String sql, final Object ... values) {
		return execute(new Co<T>() {
			@Override
			protected T run() {
				return findValue(sql, values);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T findValue(final Connection conn,
						   final String sql,
						   final Object ... values) {
		return bd(conn).findValue(sql, values);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> List<T> listValue(final String sql, final Object ... values) {
		return execute(new Co<List<T>>() {
			@Override
			protected List<T> run() {
				return listValue(sql, values);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> List<T> listValue(final Connection conn,
								 final String sql,
								 final Object ... values) {
		return bd(conn).listValue(sql, values);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T query(final Ro<T> rso,
					   final String strSQL,
					   final Object ... values) {
		return execute(new Co<T>() {
			@Override
			protected T run() {
				return query(rso, strSQL, values);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T query(final Connection conn,
					   final Ro<T> rso,
					   final String sql,
					   final Object ... values) {
		return bd(conn).query(rso, sql, values);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T execute(final Co<T> co) {
		try (val conn = getConnection()) {
            co.conn = conn;
            co.customResolver = customResolver;
			return co.run();
		}
		catch (SQLException e) {
			log.error("Execute Connection Operation Error Caused.", e);
			throw new IllegalStateException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean execute(final Connection conn, final String sql, final Object... args) {
		return bd(conn).execute(sql, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean execute(final String sql, final Object... args) {
		return execute(new Co<Boolean>() {
			@Override
			protected Boolean run() {
				return execute(sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T executeTransaction(final Co<T> co) {
		Connection conn = null;
		try {
			conn = getConnection();
			conn.setAutoCommit(false);
            co.conn = conn;
            co.customResolver = customResolver;
			T t = co.run();
			conn.commit();
			return t;
		}
		catch (Throwable e) {
			log.error("Execute Transaction Error Caused.", e);
			try {
				log.info("Rollback Transaction.");
				conn.rollback();
			}
			catch (SQLException se) {
				log.error("Execute Transaction Rollback Error Caused.", e);
				throw new IllegalStateException(se);
			}
			throw new IllegalStateException(e);
		}
		finally {
			close(conn);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int executeBatch(final String sql,
							final int batchSize,
							final Collection<?>... args) {
		return executeTransaction(new Co<Integer>() {
			@Override
			protected Integer run() {
				return executeBatch(sql, batchSize, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int executeBatch(final String sql,
							final int batchSize,
							final Collection<Collection<?>> args) {
		return executeBatch(sql, batchSize, args.toArray(new Collection<?>[0]));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int executeBatch(final Connection conn,
							final String strSQL,
							final int batchSize,
							final Collection<?>... args) {
		return bd(conn).executeBatch(strSQL, batchSize, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int executeBatch(final Connection conn,
							final String strSQL,
							final int batchSize,
							final Collection<Collection<?>> args) {
		return bd(conn).executeBatch(strSQL, batchSize, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int insert(final Connection conn,
					  final String table,
					  final Collection<String> cols,
					  final Collection<Collection<?>> args,
					  final int batchSize) {
		return bd(conn).insert(table, cols, args, batchSize);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int insert(final Connection conn, final String table, final Map<String, ?> data) {
		return bd(conn).insert(table, data);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int insert(
			final String table,
			final Collection<String> cols,
			final Collection<Collection<?>> args,
			final int batchSize) {
		return execute(new Co<Integer>() {
			@Override
			protected Integer run() {
				return insert(table, cols, args, batchSize);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int insert(final String table, final Map<String, ?> data) {
		return execute(new Co<Integer>() {
			@Override
			protected Integer run() {
				return insert(table, data);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int update(final String sql, final Object... args) {
		return execute(new Co<Integer>() {
			@Override
			protected Integer run() {
				return update(sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int update(final Connection conn,
					  final String sql,
					  final Object... args) {
		return bd(conn).update(sql, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Map<String, Object>> listMapPage(
			final Connection conn,
			final String sql,
			final Collection<Order> orders,
			int start,
			int limit,
			final Object ... args) {
		return bd(conn).listMapPage(sql, orders, start, limit, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Map<String, Object>> listMapPage(
			final String sql,
			final Collection<Order> orders,
			final int start,
			final int limit,
			final Object... args) {
		return execute(new Co<List<Map<String, Object>>>() {
			@Override
			protected List<Map<String, Object>> run() {
				return listMapPage(sql, orders, start, limit, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Map<String, Object>> listMap(
			final Connection conn,
			final String sql,
			final Object ... args) {
		return bd(conn).listMap(sql, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Map<String, Object>> listMap(
			final String sql, final Object... args) {
		return execute(new Co<List<Map<String, Object>>>() {
			@Override
			protected List<Map<String, Object>> run() {
				return listMap(sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Map<String, Object> findMap(
			final Connection conn,
			final String sql,
			final Object ... args) {
		return bd(conn).findMap(sql, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Map<String, Object> findMap(
			final String sql,
			final Object ... args) {
		return execute(new Co<Map<String, Object>>() {
			@Override
			protected Map<String, Object> run() {
				return findMap(sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T findOne(final Ro<T> ro,
						 final String sql,
						 final Object... args) {
		return execute(new Co<T>() {
			@Override
			protected T run() {
				return findOne(ro, sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> List<T> list(final Ro<T> ro,
							final String sql,
							final Object... args) {
		return execute(new Co<List<T>>() {
			@Override
			protected List<T> run() {
				return list(ro, sql, args);
			}
		});
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> T findOne(final Connection conn,
						 final Ro<T> ro,
						 final String sql,
						 final Object... args) {
		return bd(conn).findOne(ro, sql, args);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T> List<T> list(final Connection conn,
							final Ro<T> ro,
							final String sql,
							final Object... args) {
		return bd(conn).list(ro, sql, args);
	}

	// --
	// private methods

	private Connection getConnection() {
		try {
			return dataSource.getConnection();
		}
		catch (SQLException e) {
			throw new IllegalStateException(
				"Get database connection error caused", e);
		}
	}

	private void close(final Connection conn) {
		if (conn != null) {
			try {
				log.debug("Close database connection [{}].", conn);
				conn.close();
			}
			catch (SQLException e) {
				log.warn("Close connection error caused.", e);
			}
		}
	}

	private BasicJdbc bd(final Connection conn) {
		return new BasicJdbcImpl(conn, customResolver);
	}
}
